﻿using System;
using System.Configuration;
using System.IO;
using System.Reflection;
using System.Xml;
using Loki.Utils;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.Configuration;

namespace Loki.IoC
{
    public class UnityContext : LokiObject, IObjectContext
    {
        private bool _ContextLocked = false;
        private string _ContextName;
        private IUnityContainer _UnityContainer;

        /// <summary>
        /// Initializes a new instance of the <see cref="UnityContext"/> class.
        /// </summary>
        /// <param name="P_ContextName">Context name.</param>
        public UnityContext(string P_ContextName)
        {
            _UnityContainer = new UnityContainer();
            _ContextName = P_ContextName;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="UnityContext"/> class.
        /// </summary>
        /// <param name="P_ContextName">Context name.</param>
        /// <param name="P_BaseDefinitions">Base definitions URI.</param>
        public UnityContext(string P_ContextName, string P_BaseDefinitions)
        {
            try
            {
                UnityConfigurationSection L_Section = GetUnitySectionFrom(P_BaseDefinitions);
                _UnityContainer = new UnityContainer();
                _UnityContainer.LoadConfiguration(L_Section);
                _ContextName = P_ContextName;
            }
            catch (Exception Ex)
            {
                Console.Write(Ex.ToString());
                Log.Error(Ex.Message, Ex);
                throw;
            }
        }

        public void DefineSingleton<I>(I P_Singleton) where I : class
        {
            if (!_ContextLocked)
            {
                EndLoad();
            }

            _UnityContainer.RegisterInstance<I>(P_Singleton);
        }

        public void DefineSingleton<I>(I P_Singleton, string P_ObjectName) where I : class
        {
            if (!_ContextLocked)
            {
                EndLoad();
            }

            _UnityContainer.RegisterInstance<I>(P_ObjectName, P_Singleton);
        }

        public void DefineType<I, T>()
            where T : I
            where I : class
        {
            if (!_ContextLocked)
            {
                EndLoad();
            }

            _UnityContainer.RegisterType<I, T>();
        }

        public void DefineType<I, T>(string P_ObjectName)
            where T : I
            where I : class
        {
            if (!_ContextLocked)
            {
                EndLoad();
            }

            _UnityContainer.RegisterType<I, T>(P_ObjectName);
        }

        /// <summary>
        /// Marks the context as loaded.
        /// </summary>
        public void EndLoad()
        {
            _ContextLocked = true;
        }

        /// <summary>
        /// Gets an instance of type <typeparamref name="T"/> as defined in the spring
        /// configuration.
        /// </summary>
        /// <typeparam name="T">The object type.</typeparam>
        /// <param name="P_ObjectName">Name of the spring object.</param>
        /// <returns>The object.</returns>
        public T GetObject<T>(string P_ObjectName)
        {
            if (!_ContextLocked)
            {
                EndLoad();
            }

            if (IsDefined<T>(P_ObjectName))
            {
                return _UnityContainer.Resolve<T>(P_ObjectName);
            }
            else
            {
                throw BuildErrorFormat<ArgumentException>("", P_ObjectName);
            }
        }

        /// <summary>
        /// Gets an instance of type <typeparamref name="T"/> as defined in the spring
        /// configuration.
        /// </summary>
        /// <typeparam name="T">The object type.</typeparam>
        /// <returns>The object.</returns>
        public T GetObject<T>()
        {
            if (!_ContextLocked)
            {
                EndLoad();
            }

            if (IsDefined<T>())
            {
                return _UnityContainer.Resolve<T>();
            }
            else
            {
                throw BuildErrorFormat<ArgumentException>("{0}", typeof(T).Name);
            }
        }

        /// <summary>
        /// Determines whether the object with the specified name is defined.
        /// </summary>
        /// <param name="P_ObjectName">Name of the object.</param>
        /// <returns>Returns <c>true</c> if the object with the specified name is defined;
        /// otherwise, /c>.</returns>
        public bool IsDefined<T>(string P_ObjectName)
        {
            if (!_ContextLocked)
            {
                EndLoad();
            }

            return _UnityContainer.IsRegistered<T>(P_ObjectName);
        }

        /// <summary>
        /// Determines whether the object with the specified name is defined.
        /// </summary>
        /// <typeparam name="T">The required type.</typeparam>
        /// <returns>Returns <c>true</c> if the object with the specified name is defined;
        /// otherwise, /c>.</returns>
        public bool IsDefined<T>()
        {
            if (!_ContextLocked)
            {
                EndLoad();
            }

            return _UnityContainer.IsRegistered<T>();
        }

        /// <summary>
        /// Loads the additional spring context.
        /// </summary>
        /// <param name="P_ObjectDefinitionURI">The object definition URI.</param>
        public void LoadContext(params string[] P_ObjectDefinitionURI)
        {
            foreach (var L_Config in P_ObjectDefinitionURI)
            {
                UnityConfigurationSection L_Section = GetUnitySectionFrom(L_Config);
                _UnityContainer.LoadConfiguration(L_Section);
            }
        }

        public void Register<T>(Registration.ElementRegistration<T> P_Definition) where T : class
        {
            throw new NotImplementedException();
        }

        public void Release(object P_Object)
        {
            throw new NotImplementedException();
        }

        public T Resolve<T>(string P_ObjectName) where T : class
        {
            throw new NotImplementedException();
        }

        public T Resolve<T>() where T : class
        {
            throw new NotImplementedException();
        }

        public object Resolve(Type type)
        {
            throw new NotImplementedException();
        }

        private static UnityConfigurationSection GetUnitySectionFrom(string P_FileName)
        {
            string L_FileName = P_FileName;
            if (!File.Exists(P_FileName) && P_FileName.StartsWith("assembly://"))
            {
                string L_ResourceName = P_FileName;
                L_FileName = Path.GetTempFileName();

                string L_Protocol = "assembly://";

                L_ResourceName = L_ResourceName.Remove(0, L_Protocol.Length);

                string[] info = L_ResourceName.Split('/');
                if (info.Length != 3)
                {
                    throw new UriFormatException(string.Format("Invalid resource name. Name has to be in 'assembly:<assemblyName>/<namespace>/<resourceName>' format:{0}", P_FileName));
                }

                Assembly L_Assembly = Assembly.Load(info[0]);
                if (L_Assembly == null)
                {
                    throw new FileNotFoundException("Unable to load assembly [" + info[0] + "]");
                }

                Console.WriteLine(L_ResourceName);
                Console.WriteLine(string.Format("{0}.{1}", info[1], info[2]));
                Stream path = L_Assembly.GetManifestResourceStream(string.Format("{0}.{1}", info[1], info[2]));

                XmlDocument myDocument = new XmlDocument();
                myDocument.Load(path);
                path.Close();
                myDocument.PreserveWhitespace = true;
                myDocument.Save(L_FileName);
            }

            var configuration = ConfigurationManager.OpenMappedExeConfiguration(
                new ExeConfigurationFileMap { ExeConfigFilename = L_FileName },
                ConfigurationUserLevel.None);
            return configuration.GetSection("unity") as UnityConfigurationSection;
        }

        #region Disposed

        ~UnityContext()
        {
            Dispose(false);
        }

        /// <summary>
        /// Releases all resources used by an instance of the <see cref="UnityContext" /> class.
        /// </summary>
        /// <remarks>
        /// This method calls the virtual <see cref="Dispose(bool)" /> method, passing in
        /// <strong>true</strong>, and then suppresses finalization of the instance.
        /// </remarks>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Releases the unmanaged resources used by an instance of the <see cref="UnityContext" />
        /// class and optionally releases the managed resources.
        /// </summary>
        /// <param name="disposing"><strong>true</strong> to release both managed and unmanaged
        /// resources; <strong>false</strong> to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (_UnityContainer != null)
            {
                _UnityContainer.Dispose();
            }
        }

        #endregion Disposed

        public void Initialize(params IContextInstaller[] installers)
        {
            throw new NotImplementedException();
        }

        public T Get<T>(string objectName) where T : class
        {
            throw new NotImplementedException();
        }

        public T Get<T>() where T : class
        {
            throw new NotImplementedException();
        }

        public object Get(Type type)
        {
            throw new NotImplementedException();
        }

        public object Get(Type type, string objectName)
        {
            throw new NotImplementedException();
        }

        public System.Collections.Generic.IEnumerable<object> GetAll(Type type)
        {
            throw new NotImplementedException();
        }

        public System.Collections.Generic.IEnumerable<T> GetAll<T>()
        {
            throw new NotImplementedException();
        }

        public void Reset()
        {
            throw new NotImplementedException();
        }
    }
}